﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace puzzle
{
    public class RockFieldOld
    {
        private Random random;
        private List<RockTile> passable;
        private List<RockTile> border;

        private List<RockTile> targetTiles;
        private List<RockTile> targetRocks;
        private List<RockTile> rocks;
        private List<RockTile> destructibles;
        private List<RockTile> holes;
        private List<RockTile> unusedTiles;
        private RockTile startTile;
        private RockTile endTile;

        private static int holeChance = 150;
        private static double minComplexity = 2.6;
        private static int minTargetDist = 5;
        private static int blockChance = 10;
        private static double mazeness = 0.8;


        public RockFieldOld(int nbTargets, int additionalRocks)
        {
            int minMoves = 5 + nbTargets * 2;
            int baseSize = nbTargets * 30;
            int maxLoop = baseSize * 10;
            int maxRunLength = maxLoop / 2;
            int loop = 0;
            int moves = 0;
            int breakmovelog = 0;
            int breakrunlog = 0;
            int breaklog = 0;
            int brokenloops = 0;
            int maxloopnumber = 0;
            int maxlooplog = 0;
            int validitylog = 0;
            int complexitylog = 0;
            int minmoveslog = 0;
            do
            {

                random = new Random();
                passable = new List<RockTile>();
                passable.Add(new RockTile(0, 0));
                border = new List<RockTile>();
                border.Add(new RockTile(1, 0));
                border.Add(new RockTile(-1, 0));
                border.Add(new RockTile(0, 1));
                border.Add(new RockTile(0, -1));

                for (int i = 0; i < baseSize; i++)
                {
                    RockTile tile = getRandomBorder();
                    List<RockTile> pushPos = passable.Where(x => x.nextTo(tile)).ToList();
                    RockTile push = pushPos[random.Next(pushPos.Count)];
                    border.Remove(tile);
                    passable.Add(tile);

                    RockTile newBorderTile;
                    int k = random.Next(4);
                    for (int j = 0; j < 4; j++)
                    {
                        switch (k)
                        {
                            case 0:
                                newBorderTile = new RockTile(tile.X + 1, tile.Y);
                                if (!passable.Contains(newBorderTile) && !border.Contains(newBorderTile)) border.Add(newBorderTile);
                                break;
                            case 1:
                                newBorderTile = new RockTile(tile.X, tile.Y + 1);
                                if (!passable.Contains(newBorderTile) && !border.Contains(newBorderTile)) border.Add(newBorderTile);
                                break;
                            case 2:
                                newBorderTile = new RockTile(tile.X - 1, tile.Y);
                                if (!passable.Contains(newBorderTile) && !border.Contains(newBorderTile)) border.Add(newBorderTile);
                                break;
                            case 3:
                                newBorderTile = new RockTile(tile.X, tile.Y - 1);
                                if (!passable.Contains(newBorderTile) && !border.Contains(newBorderTile)) border.Add(newBorderTile);
                                break;
                        }
                        k = (k + 1) % 4;
                    }
                }

                List<RockTile> tmp = new List<RockTile>();
                foreach (RockTile tile in passable)
                {
                    if (random.Next(blockChance) == 0)
                    {
                        tmp.Add(tile);
                    }
                }

                foreach (RockTile tile in tmp)
                {
                    passable.Remove(tile);
                    border.Add(tile);
                }

                targetTiles = new List<RockTile>();
                List<RockTile> possibleTargets = getPassable().Where(t => adjacentBorderCount(t) < 3).ToList();
                for (int i = 0; i < nbTargets; i++)
                {
                    if (possibleTargets.Count > 0)
                    {
                        RockTile target = possibleTargets[random.Next(possibleTargets.Count)];
                        targetTiles.Add(target);
                        possibleTargets.RemoveAll(t => t.dist(target) < minTargetDist);
                    }
                }

                rocks = new List<RockTile>();
                targetRocks = new List<RockTile>();
                foreach (RockTile tile in targetTiles)
                {
                    rocks.Add(new RockTile(tile));
                    targetRocks.Add(new RockTile(tile));
                }

                for (int i = 0; i < additionalRocks; i++)
                {
                    if (possibleTargets.Count > 0)
                    {
                        RockTile addrock = possibleTargets[random.Next(possibleTargets.Count)];
                        rocks.Add(addrock);
                        possibleTargets.Remove(addrock);
                    }
                }

                bool nextTo = false;
                do
                {
                    nextTo = false;
                    endTile = passable[random.Next(passable.Count)];
                    foreach (RockTile tile in targetTiles)
                    {
                        if (endTile.nextTo(tile))
                        {
                            nextTo = true;
                            break;
                        }
                    }
                } while (targetTiles.Contains(endTile) || !nextTo);

                RockTile currentTile = endTile;
                List<RockTile> usedTiles = new List<RockTile>();
                destructibles = new List<RockTile>();
                holes = new List<RockTile>();
                unusedTiles = new List<RockTile>();
                List<RockTile> run = new List<RockTile>();
                
                loop = 0;
                moves = 0;
                List<RockTile> movelist = new List<RockTile>();
                RockTile move = null;
                RockTile rock = null;
                while ((moves < minMoves || !isValidStart() || getComplexity() > minComplexity) && loop < maxLoop)
                {
                    List<RockTile> possibleMoves = getPossibleMoves(currentTile);
                    if (possibleMoves.Count == 0 || run.Count > maxRunLength)
                    {
                        breaklog++;
                        if (run.Count > maxRunLength) breakrunlog++;
                        else breakmovelog++;
                        brokenloops += loop;
                        break;
                    }
                    move = possibleMoves[random.Next(possibleMoves.Count)];

                    rock = adjacentRock(currentTile);
                    if (rock != null && (move.X == rock.X || move.Y == rock.Y))
                    {
                        cleanRun(run);
                        if (targetRocks.Contains(rock))
                        {
                            targetRocks.Add(new RockTile(currentTile));
                            targetRocks.Remove(rock);
                            if (run.Count > 1) moves++;
                            //moves++;
                        }
                        rocks.Add(new RockTile(currentTile));
                        rocks.Remove(rock);
                        
                        usedTiles.AddRange(run.Where(t => !usedTiles.Contains(t)));
                        if (!destructibles.Contains(rock)) destructibles.Add(rock);
                        destructibles.RemoveAll(t => run.Contains(t));
                        run.Clear();
                        if (!usedTiles.Contains(rock)) usedTiles.Add(rock);
                        if (!usedTiles.Contains(currentTile)) usedTiles.Add(currentTile);
                        if (!usedTiles.Contains(move)) usedTiles.Add(move);
                    }
                    else
                    {
                        RockTile hole = new RockTile(currentTile.X + (currentTile.X - move.X), currentTile.Y + (currentTile.Y - move.Y));
                        if (passable.Contains(hole) && random.Next(holeChance) == 0)
                        {
                            rocks.Add(new RockTile(currentTile));
                            holes.Add(hole);
                            passable.Remove(hole);
                            cleanRun(run);
                            usedTiles.AddRange(run.Where(t => !usedTiles.Contains(t)));
                            run.Clear();
                        }
                    }

                    currentTile = move;
                    run.Add(currentTile);


                    loop++;
                }

                startTile = currentTile;
                if (!usedTiles.Contains(currentTile)) usedTiles.Add(currentTile);
                if (destructibles.Contains(currentTile)) destructibles.Remove(currentTile);

                unusedTiles = passable.Where(t => !usedTiles.Contains(t)).ToList();
                if (loop == maxLoop)
                {
                    maxlooplog++;
                    maxloopnumber += maxLoop;
                    if (!isValidStart()) validitylog++;
                    if (moves < minMoves) minmoveslog++;
                    if (getComplexity() > minComplexity) complexitylog++;
                }
            } while (loop == maxLoop || !isValidStart() || moves < minMoves || getComplexity() > minComplexity);
        }

        private RockTile getRandomBorder(){
            return border[border.Count - (int)(Math.Pow(random.NextDouble(), mazeness) * border.Count) - 1];
        }

        private RockTile adjacentRock(RockTile tile)
        {
            List<RockTile> res = new List<RockTile>();
            foreach (RockTile rock in rocks)
            {
                if (tile.nextTo(rock)) res.Add(rock);
            }
            if (res.Count == 0) return null;
            return res[random.Next(res.Count)];
        }

        private int adjacentBorderCount(RockTile tile)
        {
            int res = 0;
            foreach (RockTile b in border)
            {
                if (tile.nextTo(b)) res++;
            }
            return res;
        }

        public void cleanRun(List<RockTile> run)
        {
            for (int i = 0; i < run.Count - 1; i++)
            {
                for (int j = run.Count - 1; j > i + 1; j--)
                {
                    if (run[i].nextTo(run[j]))
                    {
                        run.RemoveRange(i + 1, j - i - 1);
                        break;
                    }
                }
            }
        }

        private List<RockTile> getPossibleMoves(RockTile currentTile)
        {
            List<RockTile> possibleMoves= new List<RockTile>();
            RockTile newTile = new RockTile(currentTile.X + 1, currentTile.Y);
            if (passable.Contains(newTile) && !rocks.Contains(newTile) && adjacentBorderCount(newTile) < 3)
            {
                possibleMoves.Add(newTile);
            } 

            newTile = new RockTile(currentTile.X, currentTile.Y + 1);
            if (passable.Contains(newTile) && !rocks.Contains(newTile) && adjacentBorderCount(newTile) < 3)
            {
                possibleMoves.Add(newTile);
            } 

            newTile = new RockTile(currentTile.X - 1, currentTile.Y);
            if (passable.Contains(newTile) && !rocks.Contains(newTile) && adjacentBorderCount(newTile) < 3)
            {
                possibleMoves.Add(newTile);
            } 

            newTile = new RockTile(currentTile.X, currentTile.Y - 1);
            if (passable.Contains(newTile) && !rocks.Contains(newTile) && adjacentBorderCount(newTile) < 3)
            {
                possibleMoves.Add(newTile);
            } 

            return possibleMoves;
        }

        public bool isValidStart()
        {
            bool res = true;
            foreach(RockTile target in targetTiles){
                if (targetRocks.Contains(target)) res = false;
            }
            return res;
        }

        public double getComplexity()
        {
            if (destructibles.Count > 0)
            {
                return (double)(passable.Count - unusedTiles.Count) / (double)destructibles.Count;
            }
            else return passable.Count - unusedTiles.Count;
        }

        public List<RockTile> getPassable()
        {
            return passable.ToList();
        }

        public List<RockTile> getBorder()
        {
            return border.ToList();
        }

        public List<RockTile> getTargets()
        {
            return targetTiles.ToList();
        }

        public List<RockTile> getRocks()
        {
            return rocks.ToList();
        }

        public List<RockTile> getTargetRocks()
        {
            return targetRocks.ToList();
        }

        public List<RockTile> getUnusedTiles()
        {
            return unusedTiles.ToList();
        }

        public RockTile getStartTile(){
            return startTile;
        }

        public List<RockTile> getDestructibles()
        {
            return destructibles.ToList();
        }

        public List<RockTile> getHoles()
        {
            return holes.ToList();
        }
    }
}
